﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using NServiceKit.Redis;
using NServiceKit.Text;

namespace RoyEngine
{
    public class Roy
    {
        private RedisClient _redisClient;
        #region 私有静态
        private static readonly Dictionary<Type, RoyDataInfo> _royDataInfosMap = new Dictionary<Type, RoyDataInfo>();

        static Roy()
        {
            __Init__();
        }

        static void __Init__()
        {
            lock (_royDataInfosMap)
            {
                if (_royDataInfosMap.Count == 0)
                {
                    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                    {
                        foreach (Type type in assembly.GetTypes())
                        {
                            if (type.IsSubclassOf(typeof(RoyDataBase)))
                            {
                                _royDataInfosMap.Add(type, ParseToRoyDataInfo(type));
                            }
                        }
                    }
                }
            }
        }

        private static readonly Dictionary<Type, ValueType> TYPE_MAP = new Dictionary<Type, ValueType>()
        {
            { typeof(bool), ValueType.Boolean},
            { typeof(int), ValueType.Int32},
            { typeof(long), ValueType.Int64},
            { typeof(string), ValueType.String},
        };

        private static RoyDataInfo ParseToRoyDataInfo(Type type)
        {
            RoyDataInfo info = new RoyDataInfo();
            var key = type.GetProperty("Id");
            if (key == null)
            {
                throw new RoyDataConvertException("RoyData must have one and only one property with RoyKeyAttribute!");
            }
            info.KeyPropertyInfo = key;
            info.ListPropertiesInfo = new List<RoyListValueInfo>();
            List<RoyValueInfo> subValues = new List<RoyValueInfo>();
            List<byte[]> subKeys = new List<byte[]>();
            foreach (PropertyInfo property in type.GetProperties())
            {
                var valueAttribute = property.GetCustomAttribute<RoyValueAttribute>();
                if (valueAttribute != null)
                {
                    if (TYPE_MAP.ContainsKey(property.PropertyType))
                    {
                        RoyValueInfo vinfo = new RoyValueInfo()
                        {
                            PropertyInfo = property,
                            ValueType = TYPE_MAP[property.PropertyType],
                        };
                        subKeys.Add(Encoding.UTF8.GetBytes(property.Name));
                        subValues.Add(vinfo);

                        if (valueAttribute.Sortable)
                        {
                            vinfo.SortSetId = GetSortSetId(type, property.Name);
                            vinfo.Sortable = true;
                            vinfo.SortSize =  valueAttribute.SortSize;
                            vinfo.SortType = valueAttribute.SortType;
                            vinfo.Desc = valueAttribute.Desc;
                        }

                        if (valueAttribute.Group)
                        {
                            vinfo.Group = true;
                        }
                    }
                    else if (property.PropertyType.IsGenericType)
                    {
                        //加载集合类型
                        Type tt = property.PropertyType.GetGenericTypeDefinition();
                        if (tt.Name == "List`1")
                        {
                            Type innerType = property.PropertyType.GenericTypeArguments[0];
                            RoyListValueInfo vinfo = new RoyListValueInfo()
                            {
                                PropertyInfo = property,
                                InnerType = TYPE_MAP[innerType],
                            };
                            info.ListPropertiesInfo.Add(vinfo);
                        }
                    }
                    else
                    {
                        throw new RoyDataConvertException(string.Format("Property type:{0} can't convert to RoyDataType", property.PropertyType.Name));
                    }
                }
            }
            info.HashPropertiesInfo = new RoyHashsetInfo()
            {
                Keys = subKeys.ToArray(),
                Properties = subValues
            };
            return info;
        }

        private static RoyDataInfo MapTo(Type type)
        {
            return _royDataInfosMap[type];
        }

        private static string GetSortSetId(Type type, string propertyName)
        {
            return string.Format("{0}:SortSet:{1}", type.Name, propertyName);
        }

        private static string GetGroupSetId(Type type, string propertyName, string groupId)
        {
            return string.Format("{0}:GroupSet:{1}:{2}",
                type.Name, propertyName, groupId);
        }
        #endregion
        
        public Roy(string host, int port)
        {
            _redisClient = new RedisClient(host, port);
        }

        public Roy(string host, int port, string password, long db = 0L)
        {
            _redisClient = new RedisClient(host, port, password, db);
        }

        public string IncrId(Type type)
        {
            long id = _redisClient.Incr(type.Name + ":Id");
            string entityId = _redisClient.Db + "db" + id;
            return entityId;
        }

        public T Get<T>(string entityId)
            where T : RoyDataBase, new()
        {
            Type type = typeof(T);

            var info = MapTo(type);
            T t = new T();
            info.KeyPropertyInfo.SetValue(t, entityId);
            string keyHash = string.Format("{0}:{1}:{2}",
                type.Name,
                entityId,
                "NormalFields");

            byte[][] data = _redisClient.HMGet(keyHash, info.HashPropertiesInfo.Keys);
            for(int i = 0; i< data.Length; i++)
            {
                byte[] bs = data[i];
                if (bs == null)
                {
                    return null;
                }
                string v = Encoding.UTF8.GetString(bs);
                var vinfo = info.HashPropertiesInfo.Properties[i];
                switch (vinfo.ValueType)
                {
                    case ValueType.Boolean:
                        vinfo.PropertyInfo.SetValue(t, Convert.ToBoolean(v));
                        break;
                    case ValueType.Int32:
                        vinfo.PropertyInfo.SetValue(t, Convert.ToInt32(v));
                        break;
                    case ValueType.Int64:
                        vinfo.PropertyInfo.SetValue(t, Convert.ToInt64(v));
                        break;
                    case ValueType.String:
                        vinfo.PropertyInfo.SetValue(t, Convert.ToString(v));
                        break;
                }
            }

            //列表属性
            foreach (var listValueInfo in info.ListPropertiesInfo)
            {
                string listId = string.Format("{0}:{1}:{2}", type.Name, entityId, listValueInfo.PropertyInfo.Name);
                switch (listValueInfo.InnerType)
                {
                    case ValueType.Boolean:
                        {
                            var list = new List<bool>();
                            var datas = _redisClient.LRange(listId, 0, -1);
                            foreach (var bs in datas)
                            {
                                string v = Encoding.UTF8.GetString(bs);
                                list.Add(Convert.ToBoolean(v));
                            }
                            listValueInfo.PropertyInfo.SetValue(t, list);
                        }
                        break;
                    case ValueType.Int32:
                        {
                            var list = new List<int>();
                            var datas = _redisClient.LRange(listId, 0, -1);
                            foreach (var bs in datas)
                            {
                                string v = Encoding.UTF8.GetString(bs);
                                list.Add(Convert.ToInt32(v));
                            }
                            listValueInfo.PropertyInfo.SetValue(t, list);
                        }
                        break;
                    case ValueType.Int64:
                        {
                            var list = new List<long>();
                            var datas = _redisClient.LRange(listId, 0, -1);
                            foreach (var bs in datas)
                            {
                                string v = Encoding.UTF8.GetString(bs);
                                list.Add(Convert.ToInt64(v));
                            }
                            listValueInfo.PropertyInfo.SetValue(t, list);
                        }
                        break;
                    case ValueType.String:
                        {
                            var list = new List<string>();
                            var datas = _redisClient.LRange(listId, 0, -1);
                            foreach (var bs in datas)
                            {
                                string v = Encoding.UTF8.GetString(bs);
                                list.Add(Convert.ToString(v));
                            }
                            listValueInfo.PropertyInfo.SetValue(t, list);
                        }
                        break;
                }
            }

            //缓存
            t.Cache = t;
            
            return t;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public long Rank<T>(T entity, string propertyName)
            where T : RoyDataBase, new()
        {
            var info = MapTo(typeof(T));
            var prop = info.HashPropertiesInfo[propertyName];
            if (!prop.Sortable)
            {
                throw new Exception(string.Format("property {0} is not sortable!", propertyName));
            }
            if (prop.Desc)
            {
                return _redisClient.ZRevRank(GetSortSetId(typeof(T), propertyName), Encoding.UTF8.GetBytes(entity.Id));
            }
            else
            {
                return _redisClient.ZRank(GetSortSetId(typeof(T), propertyName), Encoding.UTF8.GetBytes(entity.Id));
            }
        }

        public IEnumerable<T> Range<T>(string propertyName, int min, int max)
            where T : RoyDataBase, new()
        {
            byte[][] range;
            var info = MapTo(typeof (T));
            var prop = info.HashPropertiesInfo[propertyName];
            if (prop.Desc)
            {
                range = _redisClient.ZRevRange(GetSortSetId(typeof(T), propertyName), min, max);
            }
            else
            {
                range =_redisClient.ZRange(GetSortSetId(typeof(T), propertyName), min, max);
            }
            foreach (byte[] bytes in range)
            {
                string entityId = Encoding.UTF8.GetString(bytes);
                yield return Get<T>(entityId);
            }
        }

        public IEnumerable<T> GetAll<T>()
            where T : RoyDataBase, new()
        {
            byte[][] keys = _redisClient.SMembers(typeof (T).Name + ":All");
            foreach (byte[] bytes in keys)
            {
                string key = Encoding.UTF8.GetString(bytes);
                T t = Get<T>(key);
                yield return t;
            }
        } 

        public bool Save<T>(T entity)
            where T: RoyDataBase,new()
        {
            Type type = typeof (T);
            var info = MapTo(type);
            string entityId = IncrId(type);
            //var id = info.KeyPropertyInfo.GetValue(entity);
            string keyHash = string.Format("{0}:{1}:{2}", 
                type.Name,
                entityId, 
                "NormalFields");
            if (entity.Id == null)
            {
                entity.Id = entityId;
            }
            //保存Id到一个集合中
            _redisClient.SAdd(type.Name + ":All", Encoding.UTF8.GetBytes(entityId));

            //保存普通的基础属性
            byte[][] subValues = new byte[info.HashPropertiesInfo.Keys.Length][];
            for (int i = 0; i < info.HashPropertiesInfo.Keys.Length; i++)
            {
                var prop = info.HashPropertiesInfo.Properties[i];
                object value = prop.PropertyInfo.GetValue(entity);
                byte[] dataValue = Encoding.UTF8.GetBytes(value.ToString());
                subValues[i] = dataValue;

                //具有排序的属性
                if (prop.Sortable)
                {
                    switch (prop.SortType)
                    {
                        case SortType.Long:
                            _redisClient.ZAdd(prop.SortSetId, (long)value, Encoding.UTF8.GetBytes(entityId));
                            break;
                        case SortType.Double:
                            _redisClient.ZAdd(prop.SortSetId, (double)value, Encoding.UTF8.GetBytes(entityId));
                            break;
                    }
                }
                //是否具有分组属性（是否外键）
                if (prop.Group)
                {
                    string groupSetId = GetGroupSetId(type, prop.PropertyInfo.Name, value.ToString());
                    _redisClient.SAdd(groupSetId, Encoding.UTF8.GetBytes(entityId));
                }
            }
            _redisClient.HMSet(keyHash, info.HashPropertiesInfo.Keys, subValues);

            //列表类型的属性
            foreach (var listValueInfo in info.ListPropertiesInfo)
            {
                string listId = string.Format("{0}:{1}:{2}", type.Name, entityId, listValueInfo.PropertyInfo.Name);
                var listObj = listValueInfo.PropertyInfo.GetValue(entity);
                switch (listValueInfo.InnerType)
                {
                    case ValueType.Boolean:
                        SaveListValue<bool>(listId, listObj);
                        break;
                    case ValueType.Int32:
                        SaveListValue<int>(listId, listObj);
                        break;
                    case ValueType.Int64:
                        SaveListValue<long>(listId, listObj);
                        break;
                    case ValueType.String:
                        SaveListValue<string>(listId, listObj);
                        break;
                }
            }

            return true;
        }

        public bool Update<T>(T entity)
            where T: RoyDataBase,new()
        {
            Type type = typeof(T);
            var info = MapTo(type);
            string keyHash = string.Format("{0}:{1}:{2}",
                type.Name,
                entity.Id,
                "NormalFields");
            var cache = entity.Cache;

            List<byte[]> keylist = null;
            List<byte[]> valueList = null;
            foreach (var valueInfo in info.HashPropertiesInfo.Properties)
            {
                var value = valueInfo.PropertyInfo.GetValue(entity);
                var old = cache[valueInfo.PropertyInfo.Name];
                if (!value.Equals(old))
                {
                    if (keylist == null)
                    {
                        keylist = new List<byte[]>();
                        valueList = new List<byte[]>();
                    }
                    keylist.Add(Encoding.UTF8.GetBytes(valueInfo.PropertyInfo.Name));
                    valueList.Add(Encoding.UTF8.GetBytes(value.ToString()));

                    //具有排序的属性
                    if (valueInfo.Sortable)
                    {
                        switch (valueInfo.SortType)
                        {
                            case SortType.Long:
                                _redisClient.ZIncrBy(valueInfo.SortSetId, (long)value - (long)old, Encoding.UTF8.GetBytes(entity.Id));
                                break;
                            case SortType.Double:
                                _redisClient.ZIncrBy(valueInfo.SortSetId, (double)value - (double)old, Encoding.UTF8.GetBytes(entity.Id));
                                break;
                        }
                    }

                    //具有分组的属性
                    if (valueInfo.Group)
                    {
                        string oldGroupSetId = GetGroupSetId(type, valueInfo.PropertyInfo.Name, old.ToString());
                        string newGroupSetId = GetGroupSetId(type, valueInfo.PropertyInfo.Name, value.ToString());
                        var entityIdData = Encoding.UTF8.GetBytes(entity.Id);
                        _redisClient.SRem(oldGroupSetId, entityIdData);
                        _redisClient.SAdd(newGroupSetId, entityIdData);
                    }
                }
            }
            if (keylist != null)
            {
                _redisClient.HMSet(keyHash, keylist.ToArray(), valueList.ToArray());
            }

            foreach (var valueInfo in info.ListPropertiesInfo)
            {
                var value = valueInfo.PropertyInfo.GetValue(entity);
                string listId = string.Format("{0}:{1}:{2}", type.Name, entity.Id, valueInfo.PropertyInfo.Name);
                switch (valueInfo.InnerType)
                {
                    case ValueType.Boolean:
                        UpdateListValue<bool>(listId, value, cache[valueInfo.PropertyInfo.Name]);
                        break;
                    case ValueType.Int32:
                        UpdateListValue<int>(listId, value, cache[valueInfo.PropertyInfo.Name]);
                        break;
                    case ValueType.Int64:
                        UpdateListValue<long>(listId, value, cache[valueInfo.PropertyInfo.Name]);
                        break;
                    case ValueType.String:
                        UpdateListValue<string>(listId, value, cache[valueInfo.PropertyInfo.Name]);
                        break;
                }
            }

            return true;
        }

        /// <summary>
        /// 查询分组
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="propertyValue"></param>
        /// <returns></returns>
        public IEnumerable<T> Group<T>(string propertyName, object propertyValue)
            where T : RoyDataBase, new()
        {
            Type type = typeof (T);
            string groupSetId = GetGroupSetId(type, propertyName, propertyValue.ToString());
            var members = _redisClient.SMembers(groupSetId);
            foreach (byte[] member in members)
            {
                yield return Get<T>(Encoding.UTF8.GetString(member));
            }
        }

        public bool Delete<T>(T entity)
            where T : RoyDataBase, new()
        {
            if (entity == null)
                return false;
            Type type = typeof (T);
            var info = MapTo(type);
            string keyHash = string.Format("{0}:{1}:{2}",
                type.Name,
                entity.Id,
                "NormalFields");
            bool ret = _redisClient.Remove(keyHash);
            //排序
            foreach (var valueInfo in info.HashPropertiesInfo.Properties)
            {
                var value = valueInfo.PropertyInfo.GetValue(entity);

                //具有排序的属性
                if (valueInfo.Sortable)
                {
                    _redisClient.ZRem(valueInfo.SortSetId, Encoding.UTF8.GetBytes(entity.Id));
                }

                //分组
                if (valueInfo.Group)
                {
                    string groupSetId = GetGroupSetId(type, valueInfo.PropertyInfo.Name, value.ToString());
                    _redisClient.SRem(groupSetId, Encoding.UTF8.GetBytes(entity.Id));
                }
            }
            //列表
            foreach (var valueInfo in info.ListPropertiesInfo)
            {
                //var value = valueInfo.PropertyInfo.GetValue(entity);
                string listId = string.Format("{0}:{1}:{2}", type.Name, entity.Id, valueInfo.PropertyInfo.Name);
                _redisClient.Remove(listId);
            }
            return ret;
        }

        public string GetHashUId(string account)
        {
            var data = _redisClient.HGet("UIdHash", Encoding.UTF8.GetBytes(account));
            if(data != null)
                return Encoding.UTF8.GetString(data);
            return null;
        }

        public long SetHashUId(string account, string uid)
        {
            return _redisClient.HSet("UIdHash", Encoding.UTF8.GetBytes(account), Encoding.UTF8.GetBytes(uid));
        }

        public void FlushDb()
        {
            _redisClient.FlushDb();
        }

        static internal RoyDataInfo GetRoyDataInfo(RoyDataBase entity)
        {
            return MapTo(entity.GetType());
        }

        #region help functions

        void SaveListValue<T>(string listId, object value)
        {
            var list = value as List<T>;
            if (list != null)
            {
                byte[][] bytes = list.Select(x=>x.ToString()).Select(Encoding.UTF8.GetBytes).ToArray();
                _redisClient.RPush(listId, bytes);
            }
        }
        void UpdateListValue<T>(string listId, object value, object cacheValue)
        {
            var newObj = value as List<T>;
            var oldObj = cacheValue as List<T>;

            if (newObj == null || newObj.Count == 0)
            {
                _redisClient.Remove(listId);
            }
            else
            {
                bool same = true;
                for (int i = 0; i < oldObj.Count; i++)
                {
                    var newItem = newObj[i];
                    var oldItem = oldObj[i];
                    if (!newItem.Equals(oldItem))
                    {
                        same = false;
                        break;
                    }
                }
                if (!same)
                {
                    _redisClient.Remove(listId);
                    _redisClient.RPush(listId, newObj.Select(x => x.ToString()).Select(Encoding.UTF8.GetBytes).ToArray());
                }
            }
        }
        #endregion 
    }

}
